using System;
using System.Reflection;
using UnityEditor;
using UnityEngine;

namespace UnityAtoms.Editor
{
    /// <summary>
    /// Custom editor for Variables. Provides a better user workflow and indicates when which variables can be edited
    /// </summary>
    public abstract class AtomVariableEditor<T, P> : UnityEditor.Editor
        where P : IPair<T>, new()
    {
        private bool _lockedInitialValue = true;
        private bool _onEnableTriggerSectionVisible = true;

        private void DrawPotentiallyUnserializablePropertyField(SerializedProperty property, bool drawWarningWhenUnserializable = false)
        {
            if (property == null)
            {
                if (drawWarningWhenUnserializable)
                {
                    EditorGUILayout.HelpBox("Can't display values because the type is not serializable! You can still use this type, but won't be able to show values in the Editor.", MessageType.Warning);
                }
            }
            else
            {
                // Quaternion property height is not handled correctly by Unity in version
                // 2021.2 and above. Taking that into account here.
#if UNITY_2021_2_OR_NEWER
                if (property.propertyType == SerializedPropertyType.Quaternion)
                {
                    EditorGUILayout.PropertyField(property, false);
                }
                else
#endif
                {
                    EditorGUILayout.PropertyField(property, true);
                }
            }
        }

        public override void OnInspectorGUI()
        {
            serializedObject.Update();


            EditorGUILayout.PropertyField(serializedObject.FindProperty("_developerDescription"));
            EditorGUILayout.Space();

            EditorGUILayout.PropertyField(serializedObject.FindProperty("_id"));

            EditorGUILayout.BeginHorizontal();
            EditorGUI.BeginDisabledGroup(_lockedInitialValue && EditorApplication.isPlayingOrWillChangePlaymode);
            DrawPotentiallyUnserializablePropertyField(serializedObject.FindProperty("_initialValue"), drawWarningWhenUnserializable: true);
            EditorGUI.EndDisabledGroup();
            if (EditorApplication.isPlaying)
            {
                _lockedInitialValue = GUILayout.Toggle(_lockedInitialValue, "", new GUIStyle("IN LockButton") { fixedHeight = 16, margin = new RectOffset(0, 2, 4, 0) });
            }
            EditorGUILayout.EndHorizontal();


            using (new EditorGUI.DisabledGroupScope(!EditorApplication.isPlaying))
            {
                EditorGUI.BeginChangeCheck();
                DrawPotentiallyUnserializablePropertyField(serializedObject.FindProperty("_value"));
                if (EditorGUI.EndChangeCheck() && target is AtomBaseVariable atomTarget)
                {
                    try
                    {
                        var value = serializedObject.FindProperty("_value").GetPropertyValue();

                        // Doubles are for some reason deserialized to floats. The BaseValue assignment below will fail if we don't cast it to float and then to double before assignment (since the assigment itself will otherwise do a cast from object to double which will crash).
                        if (typeof(T) == typeof(double))
                        {
                            atomTarget.BaseValue = (double)(float)value;
                        }
                        //Ulong is deserialized to System32 Int.
                        else if (typeof(T) == typeof(ulong))
                        {
                            atomTarget.BaseValue = (ulong)(int)value;
                        }
                        else
                        {
                            atomTarget.BaseValue = value;
                        }

                    }
                    catch (InvalidOperationException)
                    {
                        // Deep clone the base value using JsonUtility. Otherwise oldValue and initialValue will all change when changing value.
                        var value = serializedObject.FindProperty("_value").GetGenericPropertyValue(JsonUtility.FromJson<T>(JsonUtility.ToJson(atomTarget.BaseValue)));
                        atomTarget.BaseValue = value;
                    }
                    serializedObject.Update();
                }
            }


            EditorGUI.BeginDisabledGroup(true);
            DrawPotentiallyUnserializablePropertyField(serializedObject.FindProperty("_oldValue"));
            EditorGUI.EndDisabledGroup();

            const int raiseButtonWidth = 52;

            using (new EditorGUILayout.HorizontalScope())
            {
                EditorGUILayout.PropertyField(serializedObject.FindProperty("_changed"));
                var changed = serializedObject.FindProperty("_changed").objectReferenceValue;
                if (changed != null && changed is AtomEventBase evt && target is AtomBaseVariable atomTarget)
                {
                    GUILayout.Space(2);
                    if (GUILayout.Button("Raise", GUILayout.Width(raiseButtonWidth), GUILayout.Height(EditorGUIUtility.singleLineHeight)))
                    {
                        evt.GetType().GetMethod("RaiseEditor", BindingFlags.Public | BindingFlags.Instance)?.Invoke(evt, new[] { atomTarget.BaseValue });
                    }
                }

            }

            using (new EditorGUILayout.HorizontalScope())
            {
                EditorGUILayout.PropertyField(serializedObject.FindProperty("_changedWithHistory"));
                var changedWithHistory = serializedObject.FindProperty("_changedWithHistory").objectReferenceValue;
                if (changedWithHistory != null && changedWithHistory is AtomEventBase evt && target is AtomBaseVariable atomTarget)
                {

                    GUILayout.Space(2);
                    if (GUILayout.Button("Raise", GUILayout.Width(raiseButtonWidth), GUILayout.Height(EditorGUIUtility.singleLineHeight)))
                    {
                        var oldValueProp = serializedObject.FindProperty("_oldValue");
                        object oldValue = oldValueProp.GetPropertyValue();
                        evt.GetType().GetMethod("RaiseEditor", BindingFlags.Public | BindingFlags.Instance)?.Invoke(evt, new[] { (object)(new P() { Item1 = (T)atomTarget.BaseValue, Item2 = (T)oldValue }) });
                    }
                }

            }

            EditorGUILayout.PropertyField(serializedObject.FindProperty("_preChangeTransformers"), true);

            _onEnableTriggerSectionVisible = EditorGUILayout.Foldout(_onEnableTriggerSectionVisible, "Trigger Event on OnEnable");
            if (_onEnableTriggerSectionVisible)
            {
                EditorGUILayout.PropertyField(serializedObject.FindProperty("_triggerChangedOnOnEnable"), new GUIContent("Changed"));
                EditorGUILayout.PropertyField(serializedObject.FindProperty("_triggerChangedWithHistoryOnOnEnable"), new GUIContent("ChangedWithHistory"));
            }

            serializedObject.ApplyModifiedProperties();
        }
    }
}
